// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft and others

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;                   // Size
using System.Windows.Forms;
using CoreCalc.GUI;
using CoreCalc.IO;
using CoreCalc.SheetDefinedFunctions;

namespace CoreCalc
{
  // ----------------------------------------------------------------
  // A WorkbookForm is the user interface of a workbook

  public partial class WorkbookForm : Form
  {
    private Workbook workbook;
    private Benchmarks.Benchmarks test;
    private bool autoGenerateAllFunctionsOnChange;

    public WorkbookForm(Workbook workbook, bool displayForm)
    {
      SetWorkbook(workbook);
      InitializeComponent();
      StartPosition = FormStartPosition.CenterScreen;
      Size = new Size(900, 600);
      statusRefFmt.Text = workbook.Format.RefFmt.ToString();

      //populate the mark version combobox
      foreach (string s in Enum.GetNames(typeof(MarkLevel)))
        markVersionsComboBox.Items.Add(s);
      markVersionsComboBox.SelectedIndex = (int)SdfManager.MarkLevel;

      autoGenerateAllFunctionsOnChange = false;
      DisplayWorkbook();
      if (displayForm) 
        ShowDialog();
    }

    private void SetWorkbook(Workbook newWorkbook)
    {
      if (this.workbook != null)
      {
        this.workbook.Clear();
        this.workbook.OnFunctionsAltered -= workbook_OnFunctionsAltered;
      }
      this.workbook = newWorkbook;
      this.workbook.OnFunctionsAltered += workbook_OnFunctionsAltered;
    }

    void workbook_OnFunctionsAltered(String[] functions)
    {
      if (!autoGenerateAllFunctionsOnChange)
      {
        FunctionsChangedWarning functionsChanged = new FunctionsChangedWarning(functions);
        functionsChanged.ShowDialog(this);

        if (functionsChanged.DialogResult == System.Windows.Forms.DialogResult.OK)
          SdfManager.Regenerate(functionsChanged.SelectedFunctions);
      }
      else
        SdfManager.Regenerate(functions);
    }

    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    {
      System.Environment.Exit(0);
    }

    private void aboutToolStripMenuItem1_Click(object sender, EventArgs e)
    {
      Form aboutBox = new AboutBox();
      aboutBox.ShowDialog();
    }

    private void a1ToolStripMenuItem_Click(object sender, EventArgs e)
    {
      workbook.Format.RefFmt = Formats.RefType.A1;
      Reshow();
    }

    private void c0R0ToolStripMenuItem_Click(object sender, EventArgs e)
    {
      workbook.Format.RefFmt = Formats.RefType.C0R0;
      Reshow();
    }

    private void r1C1ToolStripMenuItem_Click(object sender, EventArgs e)
    {
      workbook.Format.RefFmt = Formats.RefType.R1C1;
      Reshow();
    }

    // Add new sheet
    private void newWorkbookToolStripMenuItem_Click(object sender, EventArgs e)
    {
      InsertSheet(false);
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="functionSheet"></param>
    /// <returns>index of new sheet</returns>
    public void InsertSheet(bool functionSheet)
    {
      String name = "Sheet" + (workbook.SheetCount + 1);
      InsertSheet(functionSheet, name);
    }

    public void InsertSheet(bool functionSheet, string name)
    {
        InsertSheet(new SheetTab(this, new Sheet(workbook, name, functionSheet)), false);
    }

    private void InsertSheet(SheetTab sheetTab, bool insertToWorkbook)
    {
      if (workbook != null)
      {
        if (insertToWorkbook) 
          workbook.AddSheet(sheetTab.sheet);
        sheetHolder.TabPages.Add(sheetTab);
        sheetHolder.SelectTab(sheetTab);
      }
    }

    private void recalculateToolStripMenuItem_Click(object sender, EventArgs e)
    {
      Recalculate();
    }

    // Recalculate and reshow workbook

    private void Recalculate()
    {
      if (workbook != null)
      {
        workbook.Recalculate();
        Reshow();
      }
    }

    private void Reshow()
    {
      if (SelectedSheet != null)
        formulaBox.Text = SelectedSheet.Reshow();
      statusRefFmt.Text = workbook.Format.RefFmt.ToString();
    }

    // Copy cell
    private void copyToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.Copy();
    }

    // Delete cell
    private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.Delete();
    }

    // Insert column
    private void columnToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.InsertColumns(1);
    }

    // Insert row
    private void rowToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.InsertRows(1);
    }

    // Paste copied or cut cells, or text
    private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.Paste();
    }

    // Get selected sheet if any, else null
    private SheetTab SelectedSheet
    {
      get
      {
        return sheetHolder.TabCount > 0 ? sheetHolder.SelectedTab as SheetTab : null;
      }
    }

    public Benchmarks.Benchmarks Test
    {
      get
      {
        if (test == null) test = new Benchmarks.Benchmarks(true);
        return test;
      }
    }

    public bool AutoGenerateAllFunctionsOnChange
    {
      get { return autoGenerateAllFunctionsOnChange; }
      set { autoGenerateAllFunctionsOnChange = value; }
    }

    private void formulaBox_TextChanged(object sender, EventArgs e)
    {
      if (SelectedSheet != null)
        SelectedSheet.ChangeCurrentText(formulaBox.Text);
    }

    private void formulaBox_KeyPress(object sender, KeyPressEventArgs e)
    {
      if (SelectedSheet != null)
      {
        if (e.KeyChar == (char)Keys.Return)
        {
          SelectedSheet.SetCurrentCell(formulaBox.Text);
          e.Handled = true;
        }
        else if (e.KeyChar == (char)Keys.Escape)
        {
          SelectedSheet.Focus();
          e.Handled = true;
        }
      }
    }

    private void importSheetToolStripMenuItem_Click(object sender, EventArgs e)
    {
      OpenFileDialog ofd = new OpenFileDialog();
      WorkBookIO workbookio = new WorkBookIO();
      ofd.Filter = workbookio.SupportedFormatFilter();
      ofd.FilterIndex = workbookio.DefaultFormatIndex();
      if (ofd.ShowDialog() == DialogResult.OK)
      {
        Clear();
        SetWorkbook(workbookio.Read(ofd.FileName));
        DisplayWorkbook();
      }
    }

    private void DisplayWorkbook()
    {
      if (workbook != null)
      {
        foreach (Sheet sheet in workbook)
          sheetHolder.TabPages.Add(new SheetTab(this, sheet));
        Recalculate();
        if (sheetHolder.TabCount > 0)
          sheetHolder.SelectTab(0);
      }
    }

    public void RefreshCurrentTab()
    {
      ((SheetTab)sheetHolder.SelectedTab).RecomputeAndShow();
    }

    public void Clear()
    {
      sheetHolder.TabPages.Clear();
    }

    private void regenerateAllSDFToolStripMenuItem_Click(object sender, EventArgs e)
    {
      SdfManager.RegenerateAll();
      workbook.Recalculate();
    }

    private void executeBenchmarksToolStripMenuItem_Click(object sender, EventArgs e)
    {
        int runs = 0;
        MarkLevel selectedMarkVersion = GetVersion();
        if (int.TryParse(numberOfRunsTextBox.Text, out runs))
            Test.BenchmarkWholeWorkbook(workbook, runs, selectedMarkVersion);
    }

    public MarkLevel GetVersion()
    {
      return (MarkLevel)Enum.Parse(typeof(MarkLevel), markVersionsComboBox.SelectedItem.ToString());
    }

    private void fastImportToolStripMenuItem_Click(object sender, EventArgs e)
    {
      OpenFileDialog ofd = new OpenFileDialog();
      if (ofd.ShowDialog() == DialogResult.OK)
      {
        Clear();
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        SetWorkbook(Excel2003XMLReader.ReadFile(ofd.FileName));
        stopwatch.Stop();
        GC.GetTotalMemory(true);
        DisplayWorkbook();
      }
    }

    private void newFunctionSheetToolStripMenuItem_Click(object sender, EventArgs e)
    {
      this.InsertSheet(true);
    }

    private void sheetHolder_DrawItem(object sender, DrawItemEventArgs e)
    {
      int currentIndex = e.Index;
      Graphics g = e.Graphics;
      TabControl tc = (TabControl)sender;
      TabPage tp = tc.TabPages[currentIndex];

      Sheet currentSheet = workbook[currentIndex];
      Brush theBrush = new SolidBrush(Color.Black);
      StringFormat sf = new StringFormat();
      sf.Alignment = StringAlignment.Center;
      Color col = currentSheet.IsFunctionSheet ? Color.LightPink : tp.BackColor;
      g.FillRectangle(new SolidBrush(col), e.Bounds);
      g.DrawString(currentSheet.Name, tc.Font, theBrush, e.Bounds, sf);
    }

    private void sDFToolStripMenuItem_Click(object sender, EventArgs e)
    {
      Form sdfForm = Application.OpenForms["sdf"];
      if (sdfForm == null)
      {
        sdfForm = new SDF(this, workbook);
        sdfForm.Show();
      }
      else
      {
        ((SDF)sdfForm).PopulateFunctionListBox(true);
      }
    }

    public void SelectCellsProgrammatically(FullCellAddr[] addr)
    {
      foreach (FullCellAddr fca in addr)
      {
        sheetHolder.SelectTab(fca.sheet.Name);
        SelectedSheet.SelectCellProgrammatically(fca);
      }
    }

    public void SelectCellsProgrammatically(FullCellAddr fca)
    {
      sheetHolder.SelectTab(fca.sheet.Name);
      SelectedSheet.SelectCellProgrammatically(fca);
    }

    public void UnselectAllCells()
    {
      foreach (SheetTab st in sheetHolder.TabPages)
      {
        st.UnselectAllCells();
      }
    }

    public void ChangeCellBackgroundColor(FullCellAddr fca, Color c)
    {
      sheetHolder.SelectTab(fca.sheet.Name);
      SelectedSheet.ChangeCellBackgroundColor(fca, c);
    }

    public void ResetAllCellBackgroundColors()
    {
      foreach (SheetTab st in sheetHolder.TabPages)
      {
        st.ResetAllCellBackgroundColors();
      }
    }

    public List<FullCellAddr> GetSelectedCells()
    {
      if (SelectedSheet != null)
      {
        return SelectedSheet.GetSelectedCells();
      }
      else return new List<FullCellAddr>();
    }

    public Sheet GetSelectedSheet()
    {
      return sheetHolder.TabCount > 0 ? (sheetHolder.SelectedTab as SheetTab).sheet : null;
    }

    private void benchmarksToolStripMenuItem_Click(object sender, EventArgs e)
    {
    }

    private void markVersionsComboBox_Click(object sender, EventArgs e)
    {
    }

    private void markVersionsComboBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      SdfManager.MarkLevel = (MarkLevel)markVersionsComboBox.SelectedIndex;
      SdfManager.RegenerateAll();
    }

    private void WorkbookForm_Load(object sender, EventArgs e)
    {
        gPUToolStripMenuItem1.Checked = false;
        cPUToolStripMenuItem1.Checked = true;
    }

    private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
    {
    }

    private void showFormulasToolStripMenuItem_Click(object sender, EventArgs e)
    {
      workbook.Format.ShowFormulas = showFormulasToolStripMenuItem.Checked;
      // Wrap formula text in cells
      //DataGridViewCellStyle dgvcs = new DataGridViewCellStyle();
      //dgvcs.WrapMode = showFormulasToolStripMenuItem.Checked ? DataGridViewTriState.True : DataGridViewTriState.False;
      //foreach (SheetTab sheetTab in sheetHolder.TabPages)
      //  sheetTab.SetRowCellStyle(dgvcs);
      Reshow();
    }

    private void gPUToolStripMenuItem1_Click(object sender, EventArgs e)
    {
        Function.target = Function.TargetPlatform.GPU;
        gPUToolStripMenuItem1.Checked = true;
        cPUToolStripMenuItem1.Checked = false;
    }

    private void cPUToolStripMenuItem1_Click(object sender, EventArgs e)
    {
        Function.target = Function.TargetPlatform.CPU;
        gPUToolStripMenuItem1.Checked = false;
        cPUToolStripMenuItem1.Checked = true;
    }

    private void newParameterSheetToolStripMenuItem_Click(object sender, EventArgs e)
    {
      this.InsertSheet(false, "Param");
    }

    private void benchmarkBothTargetsToolStripMenuItem_Click(object sender, EventArgs e)
    {
        Function.target = Function.TargetPlatform.CPU;
        int runs = 0;
        MarkLevel selectedMarkVersion = GetVersion();
        if (int.TryParse(numberOfRunsTextBox.Text, out runs))
            Test.BenchmarkWholeWorkbook(workbook, runs, selectedMarkVersion);

        Function.target = Function.TargetPlatform.GPU;
        runs = 0;
        if (int.TryParse(numberOfRunsTextBox.Text, out runs))
            Test.BenchmarkWholeWorkbook(workbook, runs, selectedMarkVersion);

    }
  }

  // ----------------------------------------------------------------
  // A SheetTab is a displayed sheet: a tab page on the tab control

  public class SheetTab : TabPage
  {
    public readonly Sheet sheet;
    private readonly DataGridView dgv;
    private readonly WorkbookForm gui;

    public void SelectCellProgrammatically(FullCellAddr fca)
    {
      dgv[fca.ca.col, fca.ca.row].Selected = true;
    }

    public void UnselectAllCells()
    {
      dgv.ClearSelection();
    }

    public void SetRowCellStyle(DataGridViewCellStyle dgvcs)
    {
      dgv.RowsDefaultCellStyle = dgvcs;
    }

    public void ChangeCellBackgroundColor(FullCellAddr fca, Color c)
    {
      dgv[fca.ca.col, fca.ca.row].Style.BackColor = c;
    }

    public void ResetAllCellBackgroundColors()
    {
      //todo: highly inefficient way of doing this, refactor
      for (int c = 0; c < dgv.ColumnCount; c++)
      {
        for (int r = 0; r < dgv.RowCount; r++)
        {
          if (dgv[c, r].HasStyle) 
            dgv[c, r].Style.BackColor = Color.White;
        }
      }
    }

    public string SheetName
    {
      get { return sheet.Name; }
    }

    public bool FunctionSheet
    {
      get { return sheet.IsFunctionSheet; }
      set { sheet.IsFunctionSheet = value; }
    }

    public SheetTab(WorkbookForm gui, Sheet sheet)
      : base(sheet.Name)
    {
      this.gui = gui;
      this.sheet = sheet;
      this.Name = sheet.Name;
      this.dgv = new DataGridView();
      dgv.ShowEditingIcon = false;
      dgv.Dock = DockStyle.Fill;
      Dock = DockStyle.Fill;
      // Display formula in the current cell and computed value in other cells
      dgv.CellFormatting +=
        delegate(Object sender, DataGridViewCellFormattingEventArgs e)
        {
          int col = e.ColumnIndex, row = e.RowIndex;
          if (col == dgv.CurrentCellAddress.X && row == dgv.CurrentCellAddress.Y)
          {
            Object obj = sheet.Show(col, row);
            if (obj != null)
            {
              e.Value = obj;
              e.FormattingApplied = true;
            }
          }
          else
          {
            Object obj = sheet.ShowValue(col, row);
            if (obj != null)
            {
              e.Value = obj;
              e.FormattingApplied = true;
            }
          }
        };
      // Show current cell's address, and show formula in formula box
      dgv.CellEnter +=
        delegate(Object sender, DataGridViewCellEventArgs arg)
        {
          int row = arg.RowIndex, col = arg.ColumnIndex;
          dgv.TopLeftHeaderCell.Value = new CellAddr(col, row).ToString();
          gui.formulaBox.Text = (String)dgv.CurrentCell.FormattedValue;
        };
      // Store cell's contents (formula) in sheet after edit
      dgv.CellEndEdit +=
        delegate(Object sender, DataGridViewCellEventArgs arg)
        {
          int row = arg.RowIndex, col = arg.ColumnIndex;
          Object value = dgv.CurrentCell.Value;
          if (value != null)
            SetCell(col, row, value.ToString());
        };
      // Strange: to hold sheet, we need an extra row, but not column?
      dgv.ColumnCount = sheet.Cols;   
      dgv.RowCount = sheet.Rows + 1;
      dgv.AllowUserToAddRows = false;
      // Put labels on columns and rows:
      for (int col = 0; col < dgv.ColumnCount; col++)
        dgv.Columns[col].Name = CellAddr.ColumnName(col);
      for (int row = 0; row < dgv.RowCount; row++)
        dgv.Rows[row].HeaderCell.Value = (row + 1).ToString();
      if (sheet.IsFunctionSheet)
      {
        DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();
        cellStyle.BackColor = Color.LightPink;
        dgv.ColumnHeadersDefaultCellStyle = dgv.RowHeadersDefaultCellStyle = cellStyle;
      }
      // Somewhat arbitrary extension of the width -- could use Graphics.MeasureString("0000", dgv.Font)
      // to do it in a more principled way
      dgv.RowHeadersWidth += 20;
      Controls.Add(dgv);
    }

    // Attempt to parse s as cell contents, and set selected cell(s)

    public void SetCell(int col, int row, String text)
    {
      Cell cell = Cell.Parse(text, sheet.workbook, col, row);
      DataGridViewSelectedCellCollection dgvscc = dgv.SelectedCells;
      if (dgvscc.Count > 1 && cell is Formula)
      { // Array formula
        int ulCol = col, ulRow = row, lrCol = col, lrRow = row;
        foreach (DataGridViewCell dgvc in dgvscc)
        {
          ulCol = Math.Min(ulCol, dgvc.ColumnIndex);
          ulRow = Math.Min(ulRow, dgvc.RowIndex);
          lrCol = Math.Max(lrCol, dgvc.ColumnIndex);
          lrRow = Math.Max(lrRow, dgvc.RowIndex);
        }
        CellAddr ulCa = new CellAddr(ulCol, ulRow),
          lrCa = new CellAddr(lrCol, lrRow);
        sheet.InsertArrayFormula(cell, col, row, ulCa, lrCa);
      }
      else // One-cell formula, or constant, or null (parse error)
        sheet[col, row] = cell;
      RecomputeAndShow();
    }

    // Copy sheet's currently active cell to Clipboard, also in text format

    public void Copy()
    {
      CellAddr ca = new CellAddr(dgv.CurrentCellAddress);
      DataObject data = new DataObject();
      data.SetData(DataFormats.Text, sheet[ca].Show(ca.col, ca.row, sheet.workbook.Format));
      data.SetData(ClipboardCell.COPIED_CELL, new ClipboardCell(sheet.Name, ca));
      Clipboard.Clear();
      Clipboard.SetDataObject(data, false);
    }

    // Paste from the Clipboard.  Need to distinguish:
    // 1. Paste from CoreCalc formula Copy: preserve sharing
    // 2. Paste from CoreCalc cell Cut: adjust referring cells and this, if formula
    // 3. Paste from text (e.g. from Excel), parse to new cell

    public void Paste()
    {
      if (Clipboard.ContainsData(ClipboardCell.COPIED_CELL))
      {
        // Copy CoreCalc cell
        ClipboardCell cc = (ClipboardCell)Clipboard.GetData(ClipboardCell.COPIED_CELL);
        Console.WriteLine("Pasting copied cell " + Clipboard.GetText());
        Cell cell = sheet.workbook[cc.FromSheet][cc.FromCellAddr];
        DataGridViewSelectedCellCollection dgvscc = dgv.SelectedCells;
        foreach (DataGridViewCell dgvc in dgvscc)
          sheet.PasteCell(cell, new CellAddr(dgvc.ColumnIndex, dgvc.RowIndex), 1, 1);
        RecomputeAndShow();
      }
      else if (Clipboard.ContainsData(ClipboardCell.CUT_CELL))
      {
        // Move CoreCalc cell
        Console.WriteLine("Pasting moved cell");
      }
      else if (Clipboard.ContainsText())
      {
        // Insert text (from external source)
        CellAddr ca = new CellAddr(dgv.CurrentCellAddress);
        String text = Clipboard.GetText();
        Console.WriteLine("Pasting text " + text);
        SetCell(ca.col, ca.row, text);
      }
    }

    public void Delete()
    {
      CellAddr ca = new CellAddr(dgv.CurrentCellAddress);
      sheet[ca] = null;
      RecomputeAndShow();
    }

    public void InsertRows(int N)
    {
      sheet.InsertRowCols(dgv.CurrentCellAddress.Y, N, true /* row */);
      RecomputeAndShow();
    }

    public void InsertColumns(int N)
    {
      sheet.InsertRowCols(dgv.CurrentCellAddress.X, N, false /* column */);
      RecomputeAndShow();
    }

    public void RecomputeAndShow()
    {
      sheet.workbook.Recalculate();
      Reshow();
    }

    public String Reshow()
    {
      sheet.ShowAll(delegate(int c, int r, String value)
      {
        dgv[c, r].Value = value;
      });
      // sestoft: This seems to be the wrong place to color the sheet?
      if (FunctionSheet)
      {
        foreach (FullCellAddr fca in SdfManager.cellToFunctionMapper.addressToFunctionList.Keys)
          if (fca.sheet == sheet)
            ChangeCellBackgroundColor(fca, Color.LightCyan);
        foreach (FullCellAddr fca in SdfManager.cellToFunctionMapper.inputCellBag)
          if (fca.sheet == sheet)
            ChangeCellBackgroundColor(fca, Color.LightGreen);
        foreach (FullCellAddr fca in SdfManager.cellToFunctionMapper.outputCellBag)
          if (fca.sheet == sheet)
            ChangeCellBackgroundColor(fca, Color.LightSkyBlue);
      }
      CellAddr ca = new CellAddr(dgv.CurrentCellAddress);
      String text = null;
      // FIXME: Not clear this is needed when distinguishing Value and FormattedValue
      if (dgv.CurrentCell != null)
      {
        text = sheet.Show(ca.col, ca.row);
        dgv.CurrentCell.Value = text;
      }
      dgv.Focus();
      return text;
    }

    public void ChangeCurrentText(String text)
    {
      dgv.CurrentCell.Value = text;
    }

    public void SetCurrentCell(String text)
    {
      CellAddr ca = new CellAddr(dgv.CurrentCellAddress);
      SetCell(ca.col, ca.row, text);
    }

    public FullCellAddr GetSelectedCell()
    {
      return new FullCellAddr(dgv.CurrentCellAddress.X, dgv.CurrentCellAddress.Y, sheet);
    }

    public List<FullCellAddr> GetSelectedCells()
    {
      List<FullCellAddr> list = new List<FullCellAddr>();
      DataGridViewSelectedCellCollection selectedCells = dgv.SelectedCells;
      foreach (DataGridViewTextBoxCell d in selectedCells)
      {
        list.Add(new FullCellAddr(d.ColumnIndex, d.RowIndex, sheet));
      }
      list.Reverse();
      return list;
    }
  }

  // Package a copied cell and its cell address for holding in the Clipboard

  [Serializable]
  public class ClipboardCell
  {
    public const String COPIED_CELL = "CopiedCell";
    public const String CUT_CELL = "CutCell";
    public readonly String FromSheet;
    public readonly CellAddr FromCellAddr;

    public ClipboardCell(String fromSheet, CellAddr fromCellAddr)
    {
      this.FromSheet = fromSheet;
      this.FromCellAddr = fromCellAddr;
    }
  }
}